#include "nemu.h"
#include "memory/cache.h"
#include <time.h>
#include "burst.h"
#include <stdlib.h>

void init_cache() {
  int i;
  for (i = 0; i < CACHE_L1_SET_NUM * CACHE_L1_WAY_NUM; i++) {
    cache_L1[i].valid = false;
  }
  for (i = 0; i < CACHE_L2_SET_NUM * CACHE_L2_WAY_NUM; i++) {
    cache_L2[i].dirtyVal = false;
    cache_L2[i].valid = false;
  }
  return;
}

void ddr3_read_me(hwaddr_t addr, void* data);

int read_cache_L1(hwaddr_t addr) {
  uint32_t set = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1)); //取出地址中set的部分（右移block位并 & 1111111），set = 7 byte (set = 2^s = 128), offset = 6 byte (block = 2^e = 64)
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT));

  int way;
  int start = set * CACHE_L1_WAY_NUM;
  int end = (set + 1) * CACHE_L1_WAY_NUM;
  for (way = start; way < end; way++) { //找到相应set所在
    if (cache_L1[way].valid && cache_L1[way].tag == tag) { //如果tag和地址相符合并且alid == true
      return way;
    }
  }
  srand(time(0));
  int way_L2 = read_cache_L2(addr);
  way = start + rand() % CACHE_L1_WAY_NUM;
  memcpy(cache_L1[way].data, cache_L2[way_L2].data, CACHE_BLOCK_SIZE);

  cache_L1[way].valid = true;
  cache_L1[way].tag = tag;
  return way;
}

void ddr3_write_me(hwaddr_t addr, void* data, uint8_t* mask);

int read_cache_L2(hwaddr_t addr) {
  uint32_t set = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1));
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT));
  uint32_t block_start = ((addr >> CACHE_BLOCK_BIT) << CACHE_BLOCK_BIT);

  int way;
  int start = set * CACHE_L2_WAY_NUM;
  int end = (set + 1) * CACHE_L2_WAY_NUM;
  for (way = start; way < end; way++)
    if (cache_L2[way].valid && cache_L2[way].tag == tag) {
      return way;
    }

  srand(time(0));
  way = start + rand() % CACHE_L2_WAY_NUM;

  int i;
  if (cache_L2[way].valid && cache_L2[way].dirtyVal) {
    uint8_t tmp[BURST_LEN << 1];
    memset(tmp, 1, sizeof(tmp));
    uint32_t block_start_x = (cache_L2[way].tag << (CACHE_L2_SET_BIT + CACHE_BLOCK_BIT)) | (set << CACHE_BLOCK_BIT);
    for (i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++) {
      ddr3_write_me(block_start_x + BURST_LEN * i, cache_L2[way].data + BURST_LEN * i, tmp);
    }
  }

  for (i = 0; i < CACHE_BLOCK_SIZE / BURST_LEN; i++) {
    ddr3_read_me(block_start + BURST_LEN * i, cache_L2[way].data + BURST_LEN * i);
  }

  cache_L2[way].valid = true;
  cache_L2[way].dirtyVal = false;
  cache_L2[way].tag = tag;
  return way;
}

void dram_write(hwaddr_t addr, size_t len, uint32_t data);

void write_cache_L1(hwaddr_t addr, size_t len, uint32_t data) {
  uint32_t set = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L1_SET_NUM - 1));
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L1_SET_BIT));
  uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
  int way;
  int start = set * CACHE_L1_WAY_NUM;
  int end = (set + 1) * CACHE_L1_WAY_NUM;
  for (way = start; way < end; way++) {
    if (cache_L1[way].valid && cache_L1[way].tag == tag) {
      if (block_bias + len > CACHE_BLOCK_SIZE) {
        dram_write(addr, CACHE_BLOCK_SIZE - block_bias, data);
        memcpy(cache_L1[way].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
        write_cache_L2(addr, CACHE_BLOCK_SIZE - block_bias, data);
        write_cache_L1(addr + CACHE_BLOCK_SIZE - block_bias, len - (CACHE_BLOCK_SIZE - block_bias), data >> (CACHE_BLOCK_SIZE - block_bias));
      } else {
        dram_write(addr, len, data);
        memcpy(cache_L1[way].data + block_bias, &data, len);
        write_cache_L2(addr, len, data);
      }
      return;
    }
  }
  write_cache_L2(addr, len, data);
  return;
}

void write_cache_L2(hwaddr_t addr, size_t len, uint32_t data) {
  uint32_t set = ((addr >> CACHE_BLOCK_BIT) & (CACHE_L2_SET_NUM - 1));
  uint32_t tag = (addr >> (CACHE_BLOCK_BIT + CACHE_L2_SET_BIT));
  uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
  int way;
  int start = set * CACHE_L2_WAY_NUM;
  int end = (set + 1) * CACHE_L2_WAY_NUM;
  for (way = start; way < end; way++) {
    if (cache_L2[way].valid && cache_L2[way].tag == tag) {
      cache_L2[way].dirtyVal = true;
      if (block_bias + len > CACHE_BLOCK_SIZE) {
        memcpy(cache_L2[way].data + block_bias, &data, CACHE_BLOCK_SIZE - block_bias);
        write_cache_L2(addr + CACHE_BLOCK_SIZE - block_bias, len - (CACHE_BLOCK_SIZE - block_bias), data >> (CACHE_BLOCK_SIZE - block_bias));
      } else {
        memcpy(cache_L2[way].data + block_bias, &data, len);
      }
      return;
    }
  }
  way = read_cache_L2(addr);
  cache_L2[way].dirtyVal = true;
  memcpy(cache_L2[way].data + block_bias, &data, len);
  return;
}
